﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Observable;
using System.Collections.Specialized;

namespace ObservableUnitTests
{
    /// <summary>
    /// Summary description for ObservableDictionaryBaseTests
    /// </summary>
    [TestClass]
    public class ObservableDictionaryBaseTest
    {
        ObservableDictionaryBase<int, int, Dictionary<int, int>> ObservableDictionary;
        List<NotifyCollectionChangedEventArgs> ObservableEvents;

        [TestInitialize]
        public void TestInitialize()
        {
            Dictionary<int, int> dictionary = new Dictionary<int,int>();
            for (int i = 0; i <= 5; i++)
            {
                dictionary[i] = i;
            }

            ObservableDictionary = new ObservableDictionaryBase<int, int, Dictionary<int, int>>(dictionary);

            ObservableEvents = new List<NotifyCollectionChangedEventArgs>();

            ObservableDictionary.CollectionChanged += (s, e) => { ObservableEvents.Add(e); };

        }

        [TestMethod]
        public void Add()
        {
            int key = 10;
            int value = 20;

            ObservableDictionary[10] = 20;

            Assert.AreEqual(1, ObservableEvents.Count);

            NotifyCollectionChangedEventArgs args = ObservableEvents[0];

            Assert.AreEqual(NotifyCollectionChangedAction.Add, args.Action);
            Assert.IsNotNull(args.NewItems);
            Assert.IsNull(args.OldItems);

            Assert.AreEqual(1, args.NewItems.Count);

            Assert.AreEqual(-1, args.NewStartingIndex);
            Assert.AreEqual(-1, args.OldStartingIndex);

            KeyValuePair<int, int> pair = (KeyValuePair<int, int>) args.NewItems[0];
            Assert.AreEqual(key, pair.Key);
            Assert.AreEqual(value, pair.Value);
        }

        [TestMethod]
        public void Remove()
        {
            int key = ObservableDictionary.Keys.First();
            int value = ObservableDictionary[key];

            ObservableDictionary.Remove(key);
            
            NotifyCollectionChangedEventArgs args = ObservableEvents[0];

            Assert.AreEqual(NotifyCollectionChangedAction.Remove, args.Action);
            Assert.IsNull(args.NewItems);
            Assert.IsNotNull(args.OldItems);

            Assert.AreEqual(1, args.OldItems.Count);

            Assert.AreEqual(-1, args.NewStartingIndex);
            Assert.AreEqual(-1, args.OldStartingIndex);

            KeyValuePair<int, int> pair = (KeyValuePair<int, int>)args.OldItems[0];
            Assert.AreEqual(key, pair.Key);
            Assert.AreEqual(value, pair.Value);
        }

        [TestMethod]
        public void SetByKey_Add()
        {
            int key = 10;
            int value = 20;

            ObservableDictionary[key] = value;

            NotifyCollectionChangedEventArgs args = ObservableEvents[0];

            Assert.AreEqual(NotifyCollectionChangedAction.Add, args.Action);
            Assert.IsNotNull(args.NewItems);
            Assert.IsNull(args.OldItems);

            Assert.AreEqual(1, args.NewItems.Count);

            Assert.AreEqual(-1, args.NewStartingIndex);
            Assert.AreEqual(-1, args.OldStartingIndex);

            KeyValuePair<int, int> pair = (KeyValuePair<int, int>)args.NewItems[0];
            Assert.AreEqual(key, pair.Key);
            Assert.AreEqual(value, pair.Value);
        }

        [TestMethod]
        public void SetByKey_Replace()
        {
            int key = ObservableDictionary.Keys.First();
            int oldValue = ObservableDictionary[key];
            int newValue = oldValue + 1;

            ObservableDictionary[key] = newValue;

            NotifyCollectionChangedEventArgs args = ObservableEvents[0];

            Assert.AreEqual(NotifyCollectionChangedAction.Replace, args.Action);
            Assert.IsNotNull(args.NewItems);
            Assert.IsNotNull(args.OldItems);

            Assert.AreEqual(1, args.NewItems.Count);
            Assert.AreEqual(1, args.OldItems.Count);

            Assert.AreEqual(-1, args.NewStartingIndex);
            Assert.AreEqual(-1, args.OldStartingIndex);

            KeyValuePair<int, int> oldPair = (KeyValuePair<int, int>)args.OldItems[0];
            KeyValuePair<int, int> newPair = (KeyValuePair<int, int>)args.NewItems[0];
            
            Assert.AreEqual(key, oldPair.Key);
            Assert.AreEqual(oldValue, oldPair.Value);

            Assert.AreEqual(key, newPair.Key);
            Assert.AreEqual(newValue, newPair.Value);
        }

        [TestMethod]
        public void DictionaryTransaction_MergeAdd()
        {
            int keyBase = 100;
            int addCount = 10;

            using (var transaction = ObservableDictionary.BeginTransaction())
            {
                for (int i = 0; i < addCount; i++)
                {
                    ObservableDictionary.Add(keyBase+i, i);
                }

                //no events fired yet
                Assert.AreEqual(0, ObservableEvents.Count);
            }

            //verify
            Assert.AreEqual(1, ObservableEvents.Count);

            NotifyCollectionChangedEventArgs args = ObservableEvents[0];

            Assert.AreEqual(NotifyCollectionChangedAction.Add, args.Action);
            Assert.IsNotNull(args.NewItems);
            Assert.IsNull(args.OldItems);

            Assert.AreEqual(args.NewStartingIndex, -1);
            Assert.AreEqual(args.OldStartingIndex, -1);

            Assert.AreEqual(addCount, args.NewItems.Count);
            for (int i = 0; i < addCount; i++ )
            {
                KeyValuePair<int,int> pair = (KeyValuePair<int,int>)args.NewItems[i];
                Assert.AreEqual(keyBase + i, pair.Key);
                Assert.AreEqual(i, pair.Value);
            }
        }

        [TestMethod]
        public void DictionaryTransaction_MergeRemove()
        {
            List<KeyValuePair<int,int>> removedItems = new  List<KeyValuePair<int,int>>();

            using (var transaction = ObservableDictionary.BeginTransaction())
            {
                while (ObservableDictionary.Count > 0)
                {
                    removedItems.Add(ObservableDictionary.First());
                    ObservableDictionary.Remove(ObservableDictionary.First());
                }

                //no events fired yet
                Assert.AreEqual(0, ObservableEvents.Count);
            }

            //verify
            Assert.AreEqual(1, ObservableEvents.Count);

            NotifyCollectionChangedEventArgs args = ObservableEvents[0];

            Assert.AreEqual(NotifyCollectionChangedAction.Remove, args.Action);
            Assert.IsNull(args.NewItems);
            Assert.IsNotNull(args.OldItems);

            Assert.AreEqual(args.NewStartingIndex, -1);
            Assert.AreEqual(args.OldStartingIndex, -1);
            
            Assert.AreEqual(removedItems.Count, args.OldItems.Count);

            for (int i = 0; i < removedItems.Count; i++)
            {
                Assert.AreEqual(removedItems[i], args.OldItems[i]);
            }
        }

        
        [TestMethod]
        public void DictionaryTransaction_MultiMerge()
        {
            int keyBase = 10;
            int addCount = 10;
            int removeCount = 10;
            int expectedEventCount = 0;

            using (var transaction = ObservableDictionary.BeginTransaction())
            {
                //add 
                for (int i = 0; i < addCount; i++)
                {
                    ObservableDictionary.Add(keyBase + i, i);
                }
                expectedEventCount++;

                //remove
                for (int i = 0; i < removeCount; i++)
                {
                    ObservableDictionary.Remove(ObservableDictionary.First());
                }
                expectedEventCount++;

                //add 
                for (int i = 0; i < addCount; i++)
                {
                    ObservableDictionary.Add(keyBase+addCount+i, i);
                }
                expectedEventCount++;

                //reset
                ObservableDictionary.Clear();
                expectedEventCount += 1; //1 for remove all and 1 for reset event


                //no events fired yet
                Assert.AreEqual(0, ObservableEvents.Count);
            }

            //verify
            Assert.AreEqual(expectedEventCount, ObservableEvents.Count);
        }

        [TestMethod]
        public void PropertyChanged_SubscribeFromInitialDict()
        {
            Dictionary<int, NotifyPropertyChange> initialList = new Dictionary<int, NotifyPropertyChange>();
            for (int i = 0; i < 100; i++)
            {
                initialList.Add(i, new NotifyPropertyChange(i.ToString()));
            }

            //create observable dictionary
            ObservableDictionaryBase<int, NotifyPropertyChange, Dictionary<int, NotifyPropertyChange>> observableDict =
                   new ObservableDictionaryBase<int, NotifyPropertyChange, Dictionary<int, NotifyPropertyChange>>(initialList);

            //all PropertyChanged fired events list
            List<string> propertyNames = new List<string>();
            observableDict.PropertyChanged += (s, e) => propertyNames.Add(e.PropertyName);

            //run through all items and trigger PropertyChange events for each.
            foreach (NotifyPropertyChange o in observableDict.Values)
            {
                o.Notify();
            }

            //verify events were fired correctly
            Assert.AreEqual(initialList.Count, propertyNames.Count);
            foreach(KeyValuePair<int, NotifyPropertyChange> pair in observableDict)
            {
                Assert.IsTrue(propertyNames.Contains(pair.Value.Name));
            }
        }

        [TestMethod]
        public void PropertyChanged_SubscirbeFromAdd()
        {
            Dictionary<int, NotifyPropertyChange> initialDict = new Dictionary<int, NotifyPropertyChange>();

            ObservableDictionaryBase<int, NotifyPropertyChange, Dictionary<int, NotifyPropertyChange>> observableDict =
                   new ObservableDictionaryBase<int, NotifyPropertyChange, Dictionary<int, NotifyPropertyChange>>(initialDict);

            //add items directly to observableDict
            for (int i = 0; i < 100; i++)
            {
                observableDict.Add(i, new NotifyPropertyChange(i.ToString()));
            }

            //add list that holds all fired PropertyChanged events
            List<string> propertyNames = new List<string>();
            observableDict.PropertyChanged += (s, e) => propertyNames.Add(e.PropertyName);

            //run through all items and trigger PropertyChange events for each.
            foreach (NotifyPropertyChange o in observableDict.Values)
            {
                o.Notify();
            }

            //verify events were fired correctly
            Assert.AreEqual(initialDict.Count, propertyNames.Count);
            foreach (KeyValuePair<int, NotifyPropertyChange> pair in observableDict)
            {
                Assert.IsTrue(propertyNames.Contains(pair.Value.Name));
            }
        }

        [TestMethod]
        public void PropertyChanged_UnsubscribeFromRemove()
        {
            //create signle item under test
            int id = 1;
            NotifyPropertyChange item = new NotifyPropertyChange("item");

            //create initial list
            Dictionary<int, NotifyPropertyChange> initialDict = new Dictionary<int, NotifyPropertyChange>();
            initialDict.Add(id,item);

            //create observable collection
            ObservableDictionaryBase<int, NotifyPropertyChange, Dictionary<int, NotifyPropertyChange>> observableDict =
                   new ObservableDictionaryBase<int, NotifyPropertyChange, Dictionary<int, NotifyPropertyChange>>(initialDict);

            //create list that holds all fired PropertyChanged events
            List<string> propertyNames = new List<string>();
            observableDict.PropertyChanged += (s, e) => propertyNames.Add(e.PropertyName);

            //remove item under test from list
            observableDict.Remove(id);

            //run through all items and trigger PropertyChange events for each.
            foreach (NotifyPropertyChange o in observableDict.Values)
            {
                o.Notify();
            }

            //verify no event were fired
            Assert.AreEqual(0, propertyNames.Count);
        }

        [TestMethod]
        public void PropertyChanged_UnsubscribeAndSubscribeFromRepalce()
        {
            int id = 1;
            NotifyPropertyChange original = new NotifyPropertyChange("original");
            NotifyPropertyChange replaced = new NotifyPropertyChange("replaced");

            Dictionary<int, NotifyPropertyChange> initialDict = new Dictionary<int, NotifyPropertyChange>();

            ObservableDictionaryBase<int, NotifyPropertyChange, Dictionary<int, NotifyPropertyChange>> observableDict =
                   new ObservableDictionaryBase<int, NotifyPropertyChange, Dictionary<int, NotifyPropertyChange>>(initialDict);
            
            observableDict.Add(id, original);

            List<string> propertyNames = new List<string>();
            observableDict.PropertyChanged += (s, e) => propertyNames.Add(e.PropertyName);

            observableDict[id] = replaced;

            //run through all items and trigger PropertyChange events for each.
            foreach (NotifyPropertyChange o in observableDict.Values)
            {
                o.Notify();
            }

            //verify that exactly one event was fired and that it was for "replaced" item.
            Assert.AreEqual(1, propertyNames.Count);
            Assert.AreEqual(propertyNames[0], replaced.Name);
        }
    }
}
